package com.gmall.user.server.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Project: gmall-user
 * File: com.gmall.user.server.redis
 *
 * @author : lh.Wu
 * @date : 2020/2/10 : 16:24
 * Copyright © www.gmall.cn All Rights Reserved
 */
@Component
@Slf4j
public class CacheManager {
    public static Cache<String, String> localCache =
            CacheBuilder.newBuilder()
                    .expireAfterAccess(30, TimeUnit.SECONDS).build();


    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedisConnectionFactory factory;

    /**
     * 从缓存中取得字符串数据
     *
     * @param key
     * @return 数据
     */
    public String get(String key) {
        try {
            String value = localCache.getIfPresent(key);
            if (value == null) {
                value = redisTemplate.opsForValue().get(key);
                if (StringUtils.isNotBlank(value)) {
                    localCache.put(key, value);
                }
            }
            return value;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将数据存入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public void set(String key, String value) {
        RedisConnection connection = factory.getConnection();
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.warn("redis set  occur error,key:{},value:{},message:{}", key, value, e.getMessage());
            if (connection != null) {
                connection.close();
            }
            connection = factory.getConnection();
            connection.set(key.getBytes(), value.getBytes());
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    /**
     * 将数据存入缓存,设置过期时间
     *
     * @param key
     * @param value
     * @return
     */
    public void setEx(String key, String value, long timeout) {
        RedisConnection connection = factory.getConnection();
        try {
            redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.warn("redis setEx occur error ,key:{},value:{},timeout:{},message:{}", key, value, timeout, e.getMessage());
            if (connection != null) {
                connection.close();
            }
            connection = factory.getConnection();
            connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.ifAbsent());

        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }


    /**
     * 保存复杂类型数据到缓存
     *
     * @param key
     * @param obj
     * @return
     */
    public void saveBean(String key, Object obj) {
        try {
            set(key, JSON.toJSONString(obj));
        } catch (Exception e) {
            log.error("redis saveBean occur error ,key:{},value:{},message:{}", key, JSON.toJSONString(obj), e.getMessage());
        }
    }


    /**
     * 取得复杂JSON数据
     *
     * @param key
     * @param clazz
     * @param clazz
     * @return
     */
    public synchronized <T> T getBean(String key, Class<T> clazz) {
        String value = get(key);
        if (value == null) {
            return null;
        }
        return JSON.parseObject(value, clazz);
    }


    /**
     * 判断是否缓存了数据
     *
     * @param key 数据KEY
     * @return 判断是否缓存了
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error("redis hasKey occur error ,key:{},message:{}", key, e.getMessage());
            return false;
        }
    }


    /**
     * 根据key 递增
     *
     * @param key
     * @param value
     * @return
     */
    public Double incrDouble(String key, Double value) {
        if (value == null) {
            value = 0.0;
        }
        return redisTemplate.opsForValue().increment(key, value);
    }


    /**
     * 根据key 递减
     *
     * @param key
     * @param value
     * @return
     */
    public Double decrDouble(String key, Double value) {
        return this.incrDouble(key, -value);
    }


    /**
     * 根据key 递增
     *
     * @param key
     * @param value
     * @return
     */
    public long incrLong(String key, Integer value) {
        if (value == null) {
            value = 0;
        }
        return redisTemplate.opsForValue().increment(key, value);
    }


    /**
     * 根据key 递减
     *
     * @param key
     * @param value
     * @return
     */
    public long decrLong(String key, Integer value) {
        return this.incrLong(key, -value);
    }


    /**
     * 对 hash 中的key 进行增加
     *
     * @param key     redisKey
     * @param hashKey hashKey
     * @param count   增加值
     * @return
     */
    public long incrHashKey(String key, String hashKey, Integer count) {
        return redisTemplate.opsForHash().increment(key, hashKey, count);
    }

//////////////////////下面方法暂时没有用到

    /**
     * 判断是否缓存在指定的集合中
     *
     * @param key 数据KEY
     * @param val 数据
     * @return 判断是否缓存了
     */
    public boolean isMember(String key, String val) {

        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            return connection.sIsMember(key.getBytes(), val.getBytes());
        });
    }

    /**
     * 从缓存中删除数据
     *
     * @param key
     * @return
     */
    public void delKey(String key) {
        redisTemplate.execute((RedisCallback<Long>) connection -> connection.del(key.getBytes()));
    }

    /**
     * 设置超时时间
     *
     * @param key
     * @param seconds
     */
    public void expire(String key, int seconds) {
        redisTemplate
                .execute((RedisCallback<Boolean>) connection -> connection.expire(key.getBytes(), seconds));

    }

    /**
     * 列出set中所有成员
     *
     * @param setName set名
     * @return
     */
    public Set<Object> listSet(String setName) {

        return redisTemplate.opsForHash().keys(setName);

    }

    /**
     * 向set中追加一个值
     *
     * @param setName set名
     * @param value
     */
    public void setSave(String setName, String value) {

        redisTemplate
                .execute((RedisCallback<Long>) connection -> connection.sAdd(setName.getBytes(), value.getBytes()));

    }

    /**
     * 逆序列出sorted set包括分数的set列表
     *
     * @param key   set名
     * @param start 开始位置
     * @param end   结束位置
     * @return 列表
     */
    public Set<RedisZSetCommands.Tuple> listSortedsetRev(String key, int start, int end) {

        return redisTemplate.execute((RedisCallback<Set<RedisZSetCommands.Tuple>>) connection -> {
            return connection.zRevRangeWithScores(key.getBytes(), start, end);
        });
    }

    /**
     * 逆序取得sorted sort排名
     *
     * @param key    set名
     * @param member 成员名
     * @return 排名
     */
    public Long getRankRev(String key, String member) {

        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            return connection.zRevRank(key.getBytes(), member.getBytes());
        });

    }

    /**
     * 根据成员名取得sorted sort分数
     *
     * @param key    set名
     * @param member 成员名
     * @return 分数
     */
    public Double getMemberScore(String key, String member) {

        return redisTemplate.execute((RedisCallback<Double>) connection -> {
            return connection.zScore(key.getBytes(), member.getBytes());
        });
    }

    /**
     * 向sorted set中追加一个值
     *
     * @param key    set名
     * @param score  分数
     * @param member 成员名称
     */
    public void saveToSortedset(String key, Double score, String member) {

        redisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.zAdd(key.getBytes(), score, member.getBytes()));
    }

    /**
     * 从sorted set删除一个值
     *
     * @param key    set名
     * @param member 成员名称
     */
    public void delFromSortedset(String key, String member) {
        redisTemplate
                .execute((RedisCallback<Long>) connection -> connection.zRem(key.getBytes(), member.getBytes()));

    }

    /**
     * 从hash map中取得复杂JSON数据
     *
     * @param key
     * @param field
     * @param clazz
     */
    public <T> T getBeanFromMap(String key, String field, Class<T> clazz) {

        byte[] input = redisTemplate.execute((RedisCallback<byte[]>) connection -> {
            return connection.hGet(key.getBytes(), field.getBytes());
        });
        return JSON.parseObject(input, clazz, Feature.AutoCloseSource);
    }

    /**
     * 从hashmap中删除一个值
     *
     * @param key   map名
     * @param field 成员名称
     */
    public void delFromMap(String key, String field) {

        redisTemplate
                .execute((RedisCallback<Long>) connection -> connection.hDel(key.getBytes(), field.getBytes()));

    }


    /**
     * 根据key获取当前计数结果
     *
     * @param key
     * @return
     */
    public String getCount(String key) {

        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在，那么在进行 push 操作前会创建一个空列表
     *
     * @param <T>
     * @param key
     * @param value
     * @return
     */
    public <T> Long lpush(String key, T value) {

        return redisTemplate.opsForList().leftPush(key, JSON.toJSONString(value));
    }

    /**
     * 只有当 key 已经存在并且存着一个 list 的时候，在这个 key 下面的 list 的头部插入 value。 与 LPUSH 相反，当 key
     * 不存在的时候不会进行任何操作
     *
     * @param key
     * @param value
     * @return
     */
    public <T> Long lpushx(String key, T value) {

        return redisTemplate.opsForList().leftPushIfPresent(key, JSON.toJSONString(value));
    }

    /**
     * 返回存储在 key 里的list的长度。 如果 key 不存在，那么就被看作是空list，并且返回长度为 0
     *
     * @param key
     * @return
     */
    public Long llen(String key) {

        return redisTemplate.opsForList().size(key);
    }

    /**
     * 返回存储在 key 的列表里指定范围内的元素。 start 和 end
     * 偏移量都是基于0的下标，即list的第一个元素下标是0（list的表头），第二个元素下标是1，以此类推
     *
     * @param key
     * @return
     */
    public List<String> lrange(String key, long start, long end) {

        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 移除并且返回 key 对应的 list 的第一个元素
     *
     * @param key
     * @return
     */
    public String lpop(String key) {

        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 保存到hash集合中 只在 key 指定的哈希集中不存在指定的字段时，设置字段的值。如果 key 指定的哈希集不存在，会创建一个新的哈希集并与 key
     * 关联。如果字段已存在，该操作无效果。
     *
     * @param hName 集合名
     * @param key
     * @param value
     */
    public void hsetnx(String hName, String key, String value) {

        redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.hSetNX(key.getBytes(),
                key.getBytes(), value.getBytes()));

    }

    /**
     * 保存到hash集合中 只在 key 指定的哈希集中不存在指定的字段时，设置字段的值。如果 key 指定的哈希集不存在，会创建一个新的哈希集并与 key
     * 关联。如果字段已存在，该操作无效果。
     *
     * @param hName 集合名
     * @param key
     * @param t
     * @param <T>
     */
    public <T> void hsetnx(String hName, String key, T t) {
        hsetnx(hName, key, JSON.toJSONString(t));
    }
}
